local api = vim.api

local M = {}

---@private
local function starsetf(ft, opts)
    return {
        function(path, bufnr)
            local f = type(ft) == 'function' and ft(path, bufnr) or ft
            if not vim.g.ft_ignore_pat then
                return f
            end

            local re = vim.regex(vim.g.ft_ignore_pat)
            if not re:match_str(path) then
                return f
            end
        end,
        {
            -- Starset matches should have lowest priority by default
            priority = (opts and opts.priority) or -math.huge,
        },
    }
end

---@private
--- Get a single line or line range from the buffer.
--- If only start_lnum is specified, return a single line as a string.
--- If both start_lnum and end_lnum are omitted, return all lines from the buffer.
---
---@param bufnr number|nil The buffer to get the lines from
---@param start_lnum number|nil The line number of the first line (inclusive, 1-based)
---@param end_lnum number|nil The line number of the last line (inclusive, 1-based)
---@return table<string>|string Array of lines, or string when end_lnum is omitted
function M.getlines(bufnr, start_lnum, end_lnum)
    if end_lnum then
        -- Return a line range
        return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
    end
    if start_lnum then
        -- Return a single line
        return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
    else
        -- Return all lines
        return api.nvim_buf_get_lines(bufnr, 0, -1, false)
    end
end

---@private
--- Check whether a string matches any of the given Lua patterns.
---
---@param s string The string to check
---@param patterns table<string> A list of Lua patterns
---@return boolean `true` if s matched a pattern, else `false`
function M.findany(s, patterns)
    if s == nil then
        return false
    end
    for _, v in ipairs(patterns) do
        if s:find(v) then
            return true
        end
    end
    return false
end

---@private
--- Get the next non-whitespace line in the buffer.
---
---@param bufnr number The buffer to get the line from
---@param start_lnum number The line number of the first line to start from (inclusive, 1-based)
---@return string|nil The first non-blank line if found or `nil` otherwise
function M.nextnonblank(bufnr, start_lnum)
    for _, line in ipairs(M.getlines(bufnr, start_lnum, -1)) do
        if not line:find('^%s*$') then
            return line
        end
    end
    return nil
end

---@private
--- Check whether the given string matches the Vim regex pattern.
M.matchregex = (function()
    local cache = {}
    return function(s, pattern)
        if s == nil then
            return nil
        end
        if not cache[pattern] then
            cache[pattern] = vim.regex(pattern)
        end
        return cache[pattern]:match_str(s)
    end
end)()

-- luacheck: push no unused args
-- luacheck: push ignore 122

-- Filetypes based on file extension
---@diagnostic disable: unused-local
local extension = {
    -- BEGIN EXTENSION
    ['8th'] = '8th',
    ['a65'] = 'a65',
    aap = 'aap',
    abap = 'abap',
    abc = 'abc',
    abl = 'abel',
    wrm = 'acedb',
    ads = 'ada',
    ada = 'ada',
    gpr = 'ada',
    adb = 'ada',
    tdf = 'ahdl',
    aidl = 'aidl',
    aml = 'aml',
    run = 'ampl',
    scpt = 'applescript',
    ino = 'arduino',
    pde = 'arduino',
    art = 'art',
    asciidoc = 'asciidoc',
    adoc = 'asciidoc',
    asa = function(path, bufnr)
        if vim.g.filetype_asa then
            return vim.g.filetype_asa
        end
        return 'aspvbs'
    end,
    asm = function(path, bufnr)
        return require('vim.filetype.detect').asm(bufnr)
    end,
    lst = function(path, bufnr)
        return require('vim.filetype.detect').asm(bufnr)
    end,
    mac = function(path, bufnr)
        return require('vim.filetype.detect').asm(bufnr)
    end,
    ['asn1'] = 'asn',
    asn = 'asn',
    asp = function(path, bufnr)
        return require('vim.filetype.detect').asp(bufnr)
    end,
    astro = 'astro',
    atl = 'atlas',
    as = 'atlas',
    ahk = 'autohotkey',
    ['au3'] = 'autoit',
    ave = 'ave',
    gawk = 'awk',
    awk = 'awk',
    ref = 'b',
    imp = 'b',
    mch = 'b',
    bas = function(path, bufnr)
        return require('vim.filetype.detect').bas(bufnr)
    end,
    bi = function(path, bufnr)
        return require('vim.filetype.detect').bas(bufnr)
    end,
    bm = function(path, bufnr)
        return require('vim.filetype.detect').bas(bufnr)
    end,
    bc = 'bc',
    bdf = 'bdf',
    beancount = 'beancount',
    bib = 'bib',
    com = function(path, bufnr)
        return require('vim.filetype.detect').bindzone(bufnr, 'dcl')
    end,
    db = function(path, bufnr)
        return require('vim.filetype.detect').bindzone(bufnr, '')
    end,
    bicep = 'bicep',
    bb = 'bitbake',
    bbappend = 'bitbake',
    bbclass = 'bitbake',
    bl = 'blank',
    blp = 'blueprint',
    bsd = 'bsdl',
    bsdl = 'bsdl',
    bst = 'bst',
    btm = function(path, bufnr)
        return (vim.g.dosbatch_syntax_for_btm and vim.g.dosbatch_syntax_for_btm ~= 0) and 'dosbatch'
            or 'btm'
    end,
    bzl = 'bzl',
    bazel = 'bzl',
    BUILD = 'bzl',
    qc = 'c',
    cabal = 'cabal',
    cdl = 'cdl',
    toc = 'cdrtoc',
    cfc = 'cf',
    cfm = 'cf',
    cfi = 'cf',
    hgrc = 'cfg',
    chf = 'ch',
    chai = 'chaiscript',
    ch = function(path, bufnr)
        return require('vim.filetype.detect').change(bufnr)
    end,
    chs = 'chaskell',
    chatito = 'chatito',
    chopro = 'chordpro',
    crd = 'chordpro',
    crdpro = 'chordpro',
    cho = 'chordpro',
    chordpro = 'chordpro',
    eni = 'cl',
    icl = 'clean',
    cljx = 'clojure',
    clj = 'clojure',
    cljc = 'clojure',
    cljs = 'clojure',
    cook = 'cook',
    cmake = 'cmake',
    cmod = 'cmod',
    lib = 'cobol',
    cob = 'cobol',
    cbl = 'cobol',
    atg = 'coco',
    recipe = 'conaryrecipe',
    hook = function(path, bufnr)
        return M.getlines(bufnr, 1) == '[Trigger]' and 'conf'
    end,
    mklx = 'context',
    mkiv = 'context',
    mkii = 'context',
    mkxl = 'context',
    mkvi = 'context',
    control = function(path, bufnr)
        return require('vim.filetype.detect').control(bufnr)
    end,
    copyright = function(path, bufnr)
        return require('vim.filetype.detect').copyright(bufnr)
    end,
    csh = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    moc = 'cpp',
    hh = 'cpp',
    tlh = 'cpp',
    inl = 'cpp',
    ipp = 'cpp',
    ['c++'] = 'cpp',
    C = 'cpp',
    cxx = 'cpp',
    H = 'cpp',
    tcc = 'cpp',
    hxx = 'cpp',
    hpp = 'cpp',
    cpp = function(path, bufnr)
        return vim.g.cynlib_syntax_for_cpp and 'cynlib' or 'cpp'
    end,
    cc = function(path, bufnr)
        return vim.g.cynlib_syntax_for_cc and 'cynlib' or 'cpp'
    end,
    crm = 'crm',
    csx = 'cs',
    cs = 'cs',
    csc = 'csc',
    csdl = 'csdl',
    cshtml = 'html',
    fdr = 'csp',
    csp = 'csp',
    css = 'css',
    csv = 'csv',
    con = 'cterm',
    feature = 'cucumber',
    cuh = 'cuda',
    cu = 'cuda',
    pld = 'cupl',
    si = 'cuplsim',
    cyn = 'cynpp',
    dart = 'dart',
    drt = 'dart',
    ds = 'datascript',
    dcd = 'dcd',
    decl = function(path, bufnr)
        return require('vim.filetype.detect').decl(bufnr)
    end,
    dec = function(path, bufnr)
        return require('vim.filetype.detect').decl(bufnr)
    end,
    dcl = function(path, bufnr)
        return require('vim.filetype.detect').decl(bufnr) or 'clean'
    end,
    def = 'def',
    desc = 'desc',
    directory = 'desktop',
    desktop = 'desktop',
    diff = 'diff',
    rej = 'diff',
    Dockerfile = 'dockerfile',
    dockerfile = 'dockerfile',
    bat = 'dosbatch',
    wrap = 'dosini',
    ini = 'dosini',
    dot = 'dot',
    gv = 'dot',
    drac = 'dracula',
    drc = 'dracula',
    dtd = 'dtd',
    d = function(path, bufnr)
        return require('vim.filetype.detect').dtrace(bufnr)
    end,
    dts = 'dts',
    dtsi = 'dts',
    dylan = 'dylan',
    intr = 'dylanintr',
    lid = 'dylanlid',
    e = function(path, bufnr)
        return require('vim.filetype.detect').e(bufnr)
    end,
    E = function(path, bufnr)
        return require('vim.filetype.detect').e(bufnr)
    end,
    ecd = 'ecd',
    edf = 'edif',
    edif = 'edif',
    edo = 'edif',
    edn = function(path, bufnr)
        return require('vim.filetype.detect').edn(bufnr)
    end,
    eex = 'eelixir',
    leex = 'eelixir',
    am = function(path, bufnr)
        if not path:lower():find('makefile%.am$') then
            return 'elf'
        end
    end,
    exs = 'elixir',
    elm = 'elm',
    elv = 'elvish',
    ent = function(path, bufnr)
        return require('vim.filetype.detect').ent(bufnr)
    end,
    epp = 'epuppet',
    erl = 'erlang',
    hrl = 'erlang',
    yaws = 'erlang',
    erb = 'eruby',
    rhtml = 'eruby',
    ec = 'esqlc',
    EC = 'esqlc',
    strl = 'esterel',
    eu = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    EU = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    ew = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    EW = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    EX = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    exu = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    EXU = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    exw = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    EXW = function(path, bufnr)
        return vim.g.filetype_euphoria or 'euphoria3'
    end,
    ex = function(path, bufnr)
        return require('vim.filetype.detect').ex(bufnr)
    end,
    exp = 'expect',
    factor = 'factor',
    fal = 'falcon',
    fan = 'fan',
    fwt = 'fan',
    fnl = 'fennel',
    ['m4gl'] = 'fgl',
    ['4gl'] = 'fgl',
    ['4gh'] = 'fgl',
    fish = 'fish',
    focexec = 'focexec',
    fex = 'focexec',
    fth = 'forth',
    ft = 'forth',
    FOR = 'fortran',
    ['f77'] = 'fortran',
    ['f03'] = 'fortran',
    fortran = 'fortran',
    ['F95'] = 'fortran',
    ['f90'] = 'fortran',
    ['F03'] = 'fortran',
    fpp = 'fortran',
    FTN = 'fortran',
    ftn = 'fortran',
    ['for'] = 'fortran',
    ['F90'] = 'fortran',
    ['F77'] = 'fortran',
    ['f95'] = 'fortran',
    FPP = 'fortran',
    f = 'fortran',
    F = 'fortran',
    ['F08'] = 'fortran',
    ['f08'] = 'fortran',
    fpc = 'fpcmake',
    fsl = 'framescript',
    frm = function(path, bufnr)
        return require('vim.filetype.detect').frm(bufnr)
    end,
    fb = 'freebasic',
    fs = function(path, bufnr)
        return require('vim.filetype.detect').fs(bufnr)
    end,
    fsi = 'fsharp',
    fsx = 'fsharp',
    fusion = 'fusion',
    gdb = 'gdb',
    gdmo = 'gdmo',
    mo = 'gdmo',
    tscn = 'gdresource',
    tres = 'gdresource',
    gd = 'gdscript',
    gdshader = 'gdshader',
    shader = 'gdshader',
    ged = 'gedcom',
    gmi = 'gemtext',
    gemini = 'gemtext',
    gift = 'gift',
    gleam = 'gleam',
    glsl = 'glsl',
    gpi = 'gnuplot',
    go = 'go',
    gp = 'gp',
    gs = 'grads',
    gql = 'graphql',
    graphql = 'graphql',
    graphqls = 'graphql',
    gretl = 'gretl',
    gradle = 'groovy',
    groovy = 'groovy',
    gsp = 'gsp',
    gjs = 'javascript.glimmer',
    gts = 'typescript.glimmer',
    gyp = 'gyp',
    gypi = 'gyp',
    hack = 'hack',
    hackpartial = 'hack',
    haml = 'haml',
    hsm = 'hamster',
    hbs = 'handlebars',
    ha = 'hare',
    ['hs-boot'] = 'haskell',
    hsig = 'haskell',
    hsc = 'haskell',
    hs = 'haskell',
    ht = 'haste',
    htpp = 'hastepreproc',
    hb = 'hb',
    h = function(path, bufnr)
        return require('vim.filetype.detect').header(bufnr)
    end,
    sum = 'hercules',
    errsum = 'hercules',
    ev = 'hercules',
    vc = 'hercules',
    hcl = 'hcl',
    heex = 'heex',
    hex = 'hex',
    ['h32'] = 'hex',
    hjson = 'hjson',
    m3u = 'hlsplaylist',
    m3u8 = 'hlsplaylist',
    hog = 'hog',
    hws = 'hollywood',
    hoon = 'hoon',
    cpt = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    dtml = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    htm = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    html = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    pt = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    shtml = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    stm = function(path, bufnr)
        return require('vim.filetype.detect').html(bufnr)
    end,
    htt = 'httest',
    htb = 'httest',
    hw = function(path, bufnr)
        return require('vim.filetype.detect').hw(bufnr)
    end,
    module = function(path, bufnr)
        return require('vim.filetype.detect').hw(bufnr)
    end,
    pkg = function(path, bufnr)
        return require('vim.filetype.detect').hw(bufnr)
    end,
    iba = 'ibasic',
    ibi = 'ibasic',
    icn = 'icon',
    idl = function(path, bufnr)
        return require('vim.filetype.detect').idl(bufnr)
    end,
    inc = function(path, bufnr)
        return require('vim.filetype.detect').inc(bufnr)
    end,
    inf = 'inform',
    INF = 'inform',
    ii = 'initng',
    inp = function(path, bufnr)
        return require('vim.filetype.detect').inp(bufnr)
    end,
    ms = function(path, bufnr)
        return require('vim.filetype.detect').nroff(bufnr) or 'xmath'
    end,
    iss = 'iss',
    mst = 'ist',
    ist = 'ist',
    ijs = 'j',
    JAL = 'jal',
    jal = 'jal',
    jpr = 'jam',
    jpl = 'jam',
    jav = 'java',
    java = 'java',
    jj = 'javacc',
    jjt = 'javacc',
    es = 'javascript',
    mjs = 'javascript',
    javascript = 'javascript',
    js = 'javascript',
    jsm = 'javascript',
    cjs = 'javascript',
    jsx = 'javascriptreact',
    clp = 'jess',
    jgr = 'jgraph',
    ['j73'] = 'jovial',
    jov = 'jovial',
    jovial = 'jovial',
    properties = 'jproperties',
    slnf = 'json',
    json = 'json',
    jsonp = 'json',
    webmanifest = 'json',
    ipynb = 'json',
    ['json-patch'] = 'json',
    json5 = 'json5',
    jsonc = 'jsonc',
    jsonnet = 'jsonnet',
    libjsonnet = 'jsonnet',
    jsp = 'jsp',
    jl = 'julia',
    kv = 'kivy',
    kix = 'kix',
    kts = 'kotlin',
    kt = 'kotlin',
    ktm = 'kotlin',
    ks = 'kscript',
    k = 'kwt',
    ACE = 'lace',
    ace = 'lace',
    latte = 'latte',
    lte = 'latte',
    ld = 'ld',
    ldif = 'ldif',
    journal = 'ledger',
    ldg = 'ledger',
    ledger = 'ledger',
    less = 'less',
    lex = 'lex',
    lxx = 'lex',
    ['l++'] = 'lex',
    l = 'lex',
    lhs = 'lhaskell',
    ll = 'lifelines',
    ly = 'lilypond',
    ily = 'lilypond',
    liquid = 'liquid',
    cl = 'lisp',
    L = 'lisp',
    lisp = 'lisp',
    el = 'lisp',
    lsp = 'lisp',
    asd = 'lisp',
    lt = 'lite',
    lite = 'lite',
    lgt = 'logtalk',
    lotos = 'lotos',
    lot = 'lotos',
    lout = 'lout',
    lou = 'lout',
    ulpc = 'lpc',
    lpc = 'lpc',
    c = function(path, bufnr)
        return require('vim.filetype.detect').lpc(bufnr)
    end,
    lsl = 'lsl',
    lss = 'lss',
    nse = 'lua',
    rockspec = 'lua',
    lua = 'lua',
    lrc = 'lyrics',
    m = function(path, bufnr)
        return require('vim.filetype.detect').m(bufnr)
    end,
    at = 'm4',
    mc = function(path, bufnr)
        return require('vim.filetype.detect').mc(bufnr)
    end,
    quake = 'm3quake',
    ['m4'] = function(path, bufnr)
        path = path:lower()
        return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4'
    end,
    eml = 'mail',
    mk = 'make',
    mak = 'make',
    dsp = 'make',
    page = 'mallard',
    map = 'map',
    mws = 'maple',
    mpl = 'maple',
    mv = 'maple',
    mkdn = 'markdown',
    md = 'markdown',
    mdwn = 'markdown',
    mkd = 'markdown',
    markdown = 'markdown',
    mdown = 'markdown',
    mhtml = 'mason',
    comp = 'mason',
    mason = 'mason',
    master = 'master',
    mas = 'master',
    demo = 'maxima',
    dm1 = 'maxima',
    dm2 = 'maxima',
    dm3 = 'maxima',
    dmt = 'maxima',
    wxm = 'maxima',
    mel = 'mel',
    mf = 'mf',
    mgl = 'mgl',
    mgp = 'mgp',
    my = 'mib',
    mib = 'mib',
    mix = 'mix',
    mixal = 'mix',
    mm = function(path, bufnr)
        return require('vim.filetype.detect').mm(bufnr)
    end,
    nb = 'mma',
    mmp = 'mmp',
    mms = function(path, bufnr)
        return require('vim.filetype.detect').mms(bufnr)
    end,
    DEF = 'modula2',
    ['m2'] = 'modula2',
    mi = 'modula2',
    ssc = 'monk',
    monk = 'monk',
    tsc = 'monk',
    isc = 'monk',
    moo = 'moo',
    moon = 'moonscript',
    mp = 'mp',
    mpiv = function(path, bufnr)
        return 'mp', function(b)
            vim.b[b].mp_metafun = 1
        end
    end,
    mpvi = function(path, bufnr)
        return 'mp', function(b)
            vim.b[b].mp_metafun = 1
        end
    end,
    mpxl = function(path, bufnr)
        return 'mp', function(b)
            vim.b[b].mp_metafun = 1
        end
    end,
    mof = 'msidl',
    odl = 'msidl',
    msql = 'msql',
    mu = 'mupad',
    mush = 'mush',
    mysql = 'mysql',
    ['n1ql'] = 'n1ql',
    nql = 'n1ql',
    nanorc = 'nanorc',
    ncf = 'ncf',
    nginx = 'nginx',
    nim = 'nim',
    nims = 'nim',
    nimble = 'nim',
    ninja = 'ninja',
    nix = 'nix',
    nqc = 'nqc',
    roff = 'nroff',
    tmac = 'nroff',
    man = 'nroff',
    mom = 'nroff',
    nr = 'nroff',
    tr = 'nroff',
    nsi = 'nsis',
    nsh = 'nsis',
    obj = 'obj',
    mlt = 'ocaml',
    mly = 'ocaml',
    mll = 'ocaml',
    mlp = 'ocaml',
    mlip = 'ocaml',
    mli = 'ocaml',
    ml = 'ocaml',
    occ = 'occam',
    xom = 'omnimark',
    xin = 'omnimark',
    opam = 'opam',
    ['or'] = 'openroad',
    scad = 'openscad',
    ora = 'ora',
    org = 'org',
    org_archive = 'org',
    pxsl = 'papp',
    papp = 'papp',
    pxml = 'papp',
    pas = 'pascal',
    lpr = 'pascal',
    dpr = 'pascal',
    pbtxt = 'pbtxt',
    g = 'pccts',
    pcmk = 'pcmk',
    pdf = 'pdf',
    plx = 'perl',
    prisma = 'prisma',
    psgi = 'perl',
    al = 'perl',
    ctp = 'php',
    php = 'php',
    phpt = 'php',
    phtml = 'php',
    theme = 'php',
    pike = 'pike',
    pmod = 'pike',
    rcp = 'pilrc',
    PL = function(path, bufnr)
        return require('vim.filetype.detect').pl(bufnr)
    end,
    pli = 'pli',
    ['pl1'] = 'pli',
    ['p36'] = 'plm',
    plm = 'plm',
    pac = 'plm',
    plp = 'plp',
    pls = 'plsql',
    plsql = 'plsql',
    po = 'po',
    pot = 'po',
    pod = 'pod',
    pk = 'poke',
    ps = 'postscr',
    epsi = 'postscr',
    afm = 'postscr',
    epsf = 'postscr',
    eps = 'postscr',
    pfa = 'postscr',
    ai = 'postscr',
    pov = 'pov',
    ppd = 'ppd',
    it = 'ppwiz',
    ih = 'ppwiz',
    action = 'privoxy',
    pc = 'proc',
    pdb = 'prolog',
    pml = 'promela',
    proto = 'proto',
    ['psd1'] = 'ps1',
    ['psm1'] = 'ps1',
    ['ps1'] = 'ps1',
    pssc = 'ps1',
    ['ps1xml'] = 'ps1xml',
    psf = 'psf',
    psl = 'psl',
    pug = 'pug',
    arr = 'pyret',
    pxd = 'pyrex',
    pyx = 'pyrex',
    pyw = 'python',
    py = 'python',
    pyi = 'python',
    ptl = 'python',
    ql = 'ql',
    qll = 'ql',
    qmd = 'quarto',
    R = function(path, bufnr)
        return require('vim.filetype.detect').r(bufnr)
    end,
    rad = 'radiance',
    mat = 'radiance',
    ['pod6'] = 'raku',
    rakudoc = 'raku',
    rakutest = 'raku',
    rakumod = 'raku',
    ['pm6'] = 'raku',
    raku = 'raku',
    ['t6'] = 'raku',
    ['p6'] = 'raku',
    raml = 'raml',
    rbs = 'rbs',
    rego = 'rego',
    rem = 'remind',
    remind = 'remind',
    res = 'rescript',
    resi = 'rescript',
    frt = 'reva',
    testUnit = 'rexx',
    rex = 'rexx',
    orx = 'rexx',
    rexx = 'rexx',
    jrexx = 'rexx',
    rxj = 'rexx',
    rexxj = 'rexx',
    testGroup = 'rexx',
    rxo = 'rexx',
    Rd = 'rhelp',
    rd = 'rhelp',
    rib = 'rib',
    Rmd = 'rmd',
    rmd = 'rmd',
    smd = 'rmd',
    Smd = 'rmd',
    rnc = 'rnc',
    rng = 'rng',
    rnw = 'rnoweb',
    snw = 'rnoweb',
    Rnw = 'rnoweb',
    Snw = 'rnoweb',
    robot = 'robot',
    resource = 'robot',
    rsc = 'routeros',
    x = 'rpcgen',
    rpl = 'rpl',
    Srst = 'rrst',
    srst = 'rrst',
    Rrst = 'rrst',
    rrst = 'rrst',
    rst = 'rst',
    rtf = 'rtf',
    rjs = 'ruby',
    rxml = 'ruby',
    rb = 'ruby',
    rant = 'ruby',
    ru = 'ruby',
    rbw = 'ruby',
    gemspec = 'ruby',
    builder = 'ruby',
    rake = 'ruby',
    rs = 'rust',
    sas = 'sas',
    sass = 'sass',
    sa = 'sather',
    sbt = 'sbt',
    scala = 'scala',
    ss = 'scheme',
    scm = 'scheme',
    sld = 'scheme',
    rkt = 'scheme',
    rktd = 'scheme',
    rktl = 'scheme',
    sce = 'scilab',
    sci = 'scilab',
    scss = 'scss',
    sd = 'sd',
    sdc = 'sdc',
    pr = 'sdl',
    sdl = 'sdl',
    sed = 'sed',
    sexp = 'sexplib',
    bash = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ebuild = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    eclass = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    env = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    ksh = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
    end,
    sh = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    sieve = 'sieve',
    siv = 'sieve',
    sig = function(path, bufnr)
        return require('vim.filetype.detect').sig(bufnr)
    end,
    sil = function(path, bufnr)
        return require('vim.filetype.detect').sil(bufnr)
    end,
    sim = 'simula',
    ['s85'] = 'sinda',
    sin = 'sinda',
    ssm = 'sisu',
    sst = 'sisu',
    ssi = 'sisu',
    ['_sst'] = 'sisu',
    ['-sst'] = 'sisu',
    il = 'skill',
    ils = 'skill',
    cdf = 'skill',
    sl = 'slang',
    ice = 'slice',
    score = 'slrnsc',
    sol = 'solidity',
    tpl = 'smarty',
    ihlp = 'smcl',
    smcl = 'smcl',
    hlp = 'smcl',
    smith = 'smith',
    smt = 'smith',
    sml = 'sml',
    spt = 'snobol4',
    sno = 'snobol4',
    sln = 'solution',
    sparql = 'sparql',
    rq = 'sparql',
    spec = 'spec',
    spice = 'spice',
    sp = 'spice',
    spd = 'spup',
    spdata = 'spup',
    speedup = 'spup',
    spi = 'spyce',
    spy = 'spyce',
    tyc = 'sql',
    typ = 'sql',
    pkb = 'sql',
    tyb = 'sql',
    pks = 'sql',
    sqlj = 'sqlj',
    sqi = 'sqr',
    sqr = 'sqr',
    nut = 'squirrel',
    ['s28'] = 'srec',
    ['s37'] = 'srec',
    srec = 'srec',
    mot = 'srec',
    ['s19'] = 'srec',
    srt = 'srt',
    st = 'st',
    imata = 'stata',
    ['do'] = 'stata',
    mata = 'stata',
    ado = 'stata',
    stp = 'stp',
    quark = 'supercollider',
    sface = 'surface',
    svelte = 'svelte',
    svg = 'svg',
    swift = 'swift',
    svh = 'systemverilog',
    sv = 'systemverilog',
    tak = 'tak',
    task = 'taskedit',
    tm = 'tcl',
    tcl = 'tcl',
    itk = 'tcl',
    itcl = 'tcl',
    tk = 'tcl',
    jacl = 'tcl',
    tl = 'teal',
    tmpl = 'template',
    ti = 'terminfo',
    dtx = 'tex',
    ltx = 'tex',
    bbl = 'tex',
    latex = 'tex',
    sty = 'tex',
    cls = function(path, bufnr)
        return require('vim.filetype.detect').cls(bufnr)
    end,
    texi = 'texinfo',
    txi = 'texinfo',
    texinfo = 'texinfo',
    text = 'text',
    tfvars = 'terraform-vars',
    tla = 'tla',
    tli = 'tli',
    toml = 'toml',
    tpp = 'tpp',
    treetop = 'treetop',
    slt = 'tsalt',
    tsscl = 'tsscl',
    tssgm = 'tssgm',
    tssop = 'tssop',
    tsv = 'tsv',
    tutor = 'tutor',
    twig = 'twig',
    ts = function(path, bufnr)
        return M.getlines(bufnr, 1):find('<%?xml') and 'xml' or 'typescript'
    end,
    mts = 'typescript',
    cts = 'typescript',
    tsx = 'typescriptreact',
    uc = 'uc',
    uit = 'uil',
    uil = 'uil',
    sba = 'vb',
    vb = 'vb',
    dsm = 'vb',
    ctl = 'vb',
    vbs = 'vb',
    vdf = 'vdf',
    vdmpp = 'vdmpp',
    vpp = 'vdmpp',
    vdmrt = 'vdmrt',
    vdmsl = 'vdmsl',
    vdm = 'vdmsl',
    vr = 'vera',
    vri = 'vera',
    vrh = 'vera',
    v = 'verilog',
    va = 'verilogams',
    vams = 'verilogams',
    vhdl = 'vhdl',
    vst = 'vhdl',
    vhd = 'vhdl',
    hdl = 'vhdl',
    vho = 'vhdl',
    vbe = 'vhdl',
    vim = 'vim',
    vba = 'vim',
    mar = 'vmasm',
    cm = 'voscm',
    wrl = 'vrml',
    vroom = 'vroom',
    vue = 'vue',
    wat = 'wast',
    wast = 'wast',
    wm = 'webmacro',
    wbt = 'winbatch',
    wml = 'wml',
    wsml = 'wsml',
    ad = 'xdefaults',
    xhtml = 'xhtml',
    xht = 'xhtml',
    msc = 'xmath',
    msf = 'xmath',
    ['psc1'] = 'xml',
    tpm = 'xml',
    xliff = 'xml',
    atom = 'xml',
    xul = 'xml',
    cdxml = 'xml',
    mpd = 'xml',
    rss = 'xml',
    fsproj = 'xml',
    ui = 'xml',
    vbproj = 'xml',
    xlf = 'xml',
    wsdl = 'xml',
    csproj = 'xml',
    wpl = 'xml',
    xmi = 'xml',
    xpm = function(path, bufnr)
        return M.getlines(bufnr, 1):find('XPM2') and 'xpm2' or 'xpm'
    end,
    ['xpm2'] = 'xpm2',
    xqy = 'xquery',
    xqm = 'xquery',
    xquery = 'xquery',
    xq = 'xquery',
    xql = 'xquery',
    xs = 'xs',
    xsd = 'xsd',
    xsl = 'xslt',
    xslt = 'xslt',
    yy = 'yacc',
    ['y++'] = 'yacc',
    yxx = 'yacc',
    yml = 'yaml',
    yaml = 'yaml',
    yang = 'yang',
    ['z8a'] = 'z8a',
    zig = 'zig',
    zu = 'zimbu',
    zut = 'zimbutempl',
    zsh = 'zsh',
    vala = 'vala',
    web = function(path, bufnr)
        return require('vim.filetype.detect').web(bufnr)
    end,
    pl = function(path, bufnr)
        return require('vim.filetype.detect').pl(bufnr)
    end,
    pp = function(path, bufnr)
        return require('vim.filetype.detect').pp(bufnr)
    end,
    i = function(path, bufnr)
        return require('vim.filetype.detect').progress_asm(bufnr)
    end,
    w = function(path, bufnr)
        return require('vim.filetype.detect').progress_cweb(bufnr)
    end,
    p = function(path, bufnr)
        return require('vim.filetype.detect').progress_pascal(bufnr)
    end,
    pro = function(path, bufnr)
        return require('vim.filetype.detect').proto(bufnr, 'idlang')
    end,
    patch = function(path, bufnr)
        return require('vim.filetype.detect').patch(bufnr)
    end,
    r = function(path, bufnr)
        return require('vim.filetype.detect').r(bufnr)
    end,
    rdf = function(path, bufnr)
        return require('vim.filetype.detect').redif(bufnr)
    end,
    rules = function(path, bufnr)
        return require('vim.filetype.detect').rules(path)
    end,
    sc = function(path, bufnr)
        return require('vim.filetype.detect').sc(bufnr)
    end,
    scd = function(path, bufnr)
        return require('vim.filetype.detect').scd(bufnr)
    end,
    tcsh = function(path, bufnr)
        return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
    end,
    sql = function(path, bufnr)
        return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
    end,
    zsql = function(path, bufnr)
        return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
    end,
    tex = function(path, bufnr)
        return require('vim.filetype.detect').tex(path, bufnr)
    end,
    tf = function(path, bufnr)
        return require('vim.filetype.detect').tf(bufnr)
    end,
    txt = function(path, bufnr)
        return require('vim.filetype.detect').txt(bufnr)
    end,
    xml = function(path, bufnr)
        return require('vim.filetype.detect').xml(bufnr)
    end,
    y = function(path, bufnr)
        return require('vim.filetype.detect').y(bufnr)
    end,
    cmd = function(path, bufnr)
        return M.getlines(bufnr, 1):find('^/%*') and 'rexx' or 'dosbatch'
    end,
    rul = function(path, bufnr)
        return require('vim.filetype.detect').rul(bufnr)
    end,
    cpy = function(path, bufnr)
        return M.getlines(bufnr, 1):find('^##') and 'python' or 'cobol'
    end,
    dsl = function(path, bufnr)
        return M.getlines(bufnr, 1):find('^%s*<!') and 'dsl' or 'structurizr'
    end,
    smil = function(path, bufnr)
        return M.getlines(bufnr, 1):find('<%?%s*xml.*%?>') and 'xml' or 'smil'
    end,
    smi = function(path, bufnr)
        return require('vim.filetype.detect').smi(bufnr)
    end,
    install = function(path, bufnr)
        return require('vim.filetype.detect').install(path, bufnr)
    end,
    pm = function(path, bufnr)
        return require('vim.filetype.detect').pm(bufnr)
    end,
    me = function(path, bufnr)
        return require('vim.filetype.detect').me(path)
    end,
    reg = function(path, bufnr)
        return require('vim.filetype.detect').reg(bufnr)
    end,
    ttl = function(path, bufnr)
        return require('vim.filetype.detect').ttl(bufnr)
    end,
    rc = function(path, bufnr)
        if not path:find('/etc/Muttrc%.d/') then
            return 'rc'
        end
    end,
    rch = function(path, bufnr)
        if not path:find('/etc/Muttrc%.d/') then
            return 'rc'
        end
    end,
    class = function(path, bufnr)
        require('vim.filetype.detect').class(bufnr)
    end,
    sgml = function(path, bufnr)
        return require('vim.filetype.detect').sgml(bufnr)
    end,
    sgm = function(path, bufnr)
        return require('vim.filetype.detect').sgml(bufnr)
    end,
    t = function(path, bufnr)
        local nroff = require('vim.filetype.detect').nroff(bufnr)
        return nroff or require('vim.filetype.detect').perl(path, bufnr) or 'tads'
    end,
    -- Ignored extensions
    bak = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    ['dpkg-bak'] = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    ['dpkg-dist'] = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    ['dpkg-old'] = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    ['dpkg-new'] = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    ['in'] = function(path, bufnr)
        if vim.fs.basename(path) ~= 'configure.in' then
            local root = vim.fn.fnamemodify(path, ':r')
            return M.match({ buf = bufnr, filename = root })
        end
    end,
    new = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    old = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    orig = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    pacsave = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    pacnew = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    rpmsave = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    rmpnew = function(path, bufnr)
        local root = vim.fn.fnamemodify(path, ':r')
        return M.match({ buf = bufnr, filename = root })
    end,
    -- END EXTENSION
}

local filename = {
    -- BEGIN FILENAME
    ['a2psrc'] = 'a2ps',
    ['/etc/a2ps.cfg'] = 'a2ps',
    ['.a2psrc'] = 'a2ps',
    ['.asoundrc'] = 'alsaconf',
    ['/usr/share/alsa/alsa.conf'] = 'alsaconf',
    ['/etc/asound.conf'] = 'alsaconf',
    ['build.xml'] = 'ant',
    ['.htaccess'] = 'apache',
    ['apt.conf'] = 'aptconf',
    ['/.aptitude/config'] = 'aptconf',
    ['=tagging-method'] = 'arch',
    ['.arch-inventory'] = 'arch',
    ['GNUmakefile.am'] = 'automake',
    ['named.root'] = 'bindzone',
    WORKSPACE = 'bzl',
    BUILD = 'bzl',
    ['cabal.project'] = 'cabalproject',
    ['cabal.config'] = 'cabalconfig',
    calendar = 'calendar',
    catalog = 'catalog',
    ['/etc/cdrdao.conf'] = 'cdrdaoconf',
    ['.cdrdao'] = 'cdrdaoconf',
    ['/etc/default/cdrdao'] = 'cdrdaoconf',
    ['/etc/defaults/cdrdao'] = 'cdrdaoconf',
    ['cfengine.conf'] = 'cfengine',
    ['CMakeLists.txt'] = 'cmake',
    ['.alias'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['.cshrc'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['.login'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['csh.cshrc'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['csh.login'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['csh.logout'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['auto.master'] = 'conf',
    ['configure.in'] = 'config',
    ['configure.ac'] = 'config',
    crontab = 'crontab',
    ['.cvsrc'] = 'cvsrc',
    ['/debian/changelog'] = 'debchangelog',
    ['changelog.dch'] = 'debchangelog',
    ['changelog.Debian'] = 'debchangelog',
    ['NEWS.dch'] = 'debchangelog',
    ['NEWS.Debian'] = 'debchangelog',
    ['/debian/control'] = 'debcontrol',
    ['/debian/copyright'] = 'debcopyright',
    ['/etc/apt/sources.list'] = 'debsources',
    ['denyhosts.conf'] = 'denyhosts',
    ['dict.conf'] = 'dictconf',
    ['.dictrc'] = 'dictconf',
    ['/etc/DIR_COLORS'] = 'dircolors',
    ['.dir_colors'] = 'dircolors',
    ['.dircolors'] = 'dircolors',
    ['/etc/dnsmasq.conf'] = 'dnsmasq',
    Containerfile = 'dockerfile',
    dockerfile = 'dockerfile',
    Dockerfile = 'dockerfile',
    npmrc = 'dosini',
    ['/etc/yum.conf'] = 'dosini',
    ['.npmrc'] = 'dosini',
    ['.editorconfig'] = 'dosini',
    ['/etc/pacman.conf'] = 'confini',
    ['mpv.conf'] = 'confini',
    dune = 'dune',
    jbuild = 'dune',
    ['dune-workspace'] = 'dune',
    ['dune-project'] = 'dune',
    ['elinks.conf'] = 'elinks',
    ['mix.lock'] = 'elixir',
    ['filter-rules'] = 'elmfilt',
    ['exim.conf'] = 'exim',
    exports = 'exports',
    ['.fetchmailrc'] = 'fetchmail',
    fvSchemes = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    fvSolution = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    fvConstraints = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    fvModels = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    fstab = 'fstab',
    mtab = 'fstab',
    ['.gdbinit'] = 'gdb',
    gdbinit = 'gdb',
    ['.gdbearlyinit'] = 'gdb',
    gdbearlyinit = 'gdb',
    ['lltxxxxx.txt'] = 'gedcom',
    ['TAG_EDITMSG'] = 'gitcommit',
    ['MERGE_MSG'] = 'gitcommit',
    ['COMMIT_EDITMSG'] = 'gitcommit',
    ['NOTES_EDITMSG'] = 'gitcommit',
    ['EDIT_DESCRIPTION'] = 'gitcommit',
    ['.gitconfig'] = 'gitconfig',
    ['.gitmodules'] = 'gitconfig',
    ['.gitattributes'] = 'gitattributes',
    ['.gitignore'] = 'gitignore',
    ['gitolite.conf'] = 'gitolite',
    ['git-rebase-todo'] = 'gitrebase',
    gkrellmrc = 'gkrellmrc',
    ['.gnashrc'] = 'gnash',
    ['.gnashpluginrc'] = 'gnash',
    gnashpluginrc = 'gnash',
    gnashrc = 'gnash',
    ['.gnuplot'] = 'gnuplot',
    ['go.work'] = 'gowork',
    ['.gprc'] = 'gp',
    ['/.gnupg/gpg.conf'] = 'gpg',
    ['/.gnupg/options'] = 'gpg',
    ['/var/backups/gshadow.bak'] = 'group',
    ['/etc/gshadow'] = 'group',
    ['/etc/group-'] = 'group',
    ['/etc/gshadow.edit'] = 'group',
    ['/etc/gshadow-'] = 'group',
    ['/etc/group'] = 'group',
    ['/var/backups/group.bak'] = 'group',
    ['/etc/group.edit'] = 'group',
    ['/boot/grub/menu.lst'] = 'grub',
    ['/etc/grub.conf'] = 'grub',
    ['/boot/grub/grub.conf'] = 'grub',
    ['.gtkrc'] = 'gtkrc',
    gtkrc = 'gtkrc',
    ['snort.conf'] = 'hog',
    ['vision.conf'] = 'hog',
    ['/etc/host.conf'] = 'hostconf',
    ['/etc/hosts.allow'] = 'hostsaccess',
    ['/etc/hosts.deny'] = 'hostsaccess',
    ['/.icewm/menu'] = 'icemenu',
    ['.indent.pro'] = 'indent',
    indentrc = 'indent',
    inittab = 'inittab',
    ['ipf.conf'] = 'ipfilter',
    ['ipf6.conf'] = 'ipfilter',
    ['ipf.rules'] = 'ipfilter',
    ['.eslintrc'] = 'json',
    ['.babelrc'] = 'json',
    ['Pipfile.lock'] = 'json',
    ['.firebaserc'] = 'json',
    ['.prettierrc'] = 'json',
    Kconfig = 'kconfig',
    ['Kconfig.debug'] = 'kconfig',
    ['lftp.conf'] = 'lftp',
    ['.lftprc'] = 'lftp',
    ['/.libao'] = 'libao',
    ['/etc/libao.conf'] = 'libao',
    ['lilo.conf'] = 'lilo',
    ['/etc/limits'] = 'limits',
    ['.emacs'] = 'lisp',
    sbclrc = 'lisp',
    ['.sbclrc'] = 'lisp',
    ['.sawfishrc'] = 'lisp',
    ['/etc/login.access'] = 'loginaccess',
    ['/etc/login.defs'] = 'logindefs',
    ['.luacheckrc'] = 'lua',
    ['lynx.cfg'] = 'lynx',
    ['m3overrides'] = 'm3build',
    ['m3makefile'] = 'm3build',
    ['cm3.cfg'] = 'm3quake',
    ['.followup'] = 'mail',
    ['.article'] = 'mail',
    ['.letter'] = 'mail',
    ['/etc/aliases'] = 'mailaliases',
    ['/etc/mail/aliases'] = 'mailaliases',
    mailcap = 'mailcap',
    ['.mailcap'] = 'mailcap',
    ['/etc/man.conf'] = 'manconf',
    ['man.config'] = 'manconf',
    ['maxima-init.mac'] = 'maxima',
    ['meson.build'] = 'meson',
    ['meson_options.txt'] = 'meson',
    ['/etc/conf.modules'] = 'modconf',
    ['/etc/modules'] = 'modconf',
    ['/etc/modules.conf'] = 'modconf',
    ['/.mplayer/config'] = 'mplayerconf',
    ['mplayer.conf'] = 'mplayerconf',
    mrxvtrc = 'mrxvtrc',
    ['.mrxvtrc'] = 'mrxvtrc',
    ['/etc/nanorc'] = 'nanorc',
    Neomuttrc = 'neomuttrc',
    ['.netrc'] = 'netrc',
    NEWS = function(path, bufnr)
        return require('vim.filetype.detect').news(bufnr)
    end,
    ['.ocamlinit'] = 'ocaml',
    ['.octaverc'] = 'octave',
    octaverc = 'octave',
    ['octave.conf'] = 'octave',
    opam = 'opam',
    ['/etc/pam.conf'] = 'pamconf',
    ['pam_env.conf'] = 'pamenv',
    ['.pam_environment'] = 'pamenv',
    ['/var/backups/passwd.bak'] = 'passwd',
    ['/var/backups/shadow.bak'] = 'passwd',
    ['/etc/passwd'] = 'passwd',
    ['/etc/passwd-'] = 'passwd',
    ['/etc/shadow.edit'] = 'passwd',
    ['/etc/shadow-'] = 'passwd',
    ['/etc/shadow'] = 'passwd',
    ['/etc/passwd.edit'] = 'passwd',
    ['latexmkrc'] = 'perl',
    ['.latexmkrc'] = 'perl',
    ['pf.conf'] = 'pf',
    ['main.cf'] = 'pfmain',
    pinerc = 'pine',
    ['.pinercex'] = 'pine',
    ['.pinerc'] = 'pine',
    pinercex = 'pine',
    ['/etc/pinforc'] = 'pinfo',
    ['/.pinforc'] = 'pinfo',
    ['.povrayrc'] = 'povini',
    ['printcap'] = function(path, bufnr)
        return 'ptcap', function(b)
            vim.b[b].ptcap_type = 'print'
        end
    end,
    ['termcap'] = function(path, bufnr)
        return 'ptcap', function(b)
            vim.b[b].ptcap_type = 'term'
        end
    end,
    ['.procmailrc'] = 'procmail',
    ['.procmail'] = 'procmail',
    ['indent.pro'] = function(path, bufnr)
        return require('vim.filetype.detect').proto(bufnr, 'indent')
    end,
    ['/etc/protocols'] = 'protocols',
    ['INDEX'] = function(path, bufnr)
        return require('vim.filetype.detect').psf(bufnr)
    end,
    ['INFO'] = function(path, bufnr)
        return require('vim.filetype.detect').psf(bufnr)
    end,
    ['.pythonstartup'] = 'python',
    ['.pythonrc'] = 'python',
    SConstruct = 'python',
    ratpoisonrc = 'ratpoison',
    ['.ratpoisonrc'] = 'ratpoison',
    inputrc = 'readline',
    ['.inputrc'] = 'readline',
    ['.reminders'] = 'remind',
    ['resolv.conf'] = 'resolv',
    ['robots.txt'] = 'robots',
    Gemfile = 'ruby',
    Puppetfile = 'ruby',
    ['.irbrc'] = 'ruby',
    irbrc = 'ruby',
    Vagrantfile = 'ruby',
    ['smb.conf'] = 'samba',
    screenrc = 'screen',
    ['.screenrc'] = 'screen',
    ['/etc/sensors3.conf'] = 'sensors',
    ['/etc/sensors.conf'] = 'sensors',
    ['/etc/services'] = 'services',
    ['/etc/serial.conf'] = 'setserial',
    ['/etc/udev/cdsymlinks.conf'] = 'sh',
    ['bash.bashrc'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    bashrc = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['.bashrc'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['.env'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    ['.kshrc'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
    end,
    ['.profile'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    ['/etc/profile'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    APKBUILD = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    PKGBUILD = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['.tcshrc'] = function(path, bufnr)
        return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
    end,
    ['tcsh.login'] = function(path, bufnr)
        return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
    end,
    ['tcsh.tcshrc'] = function(path, bufnr)
        return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
    end,
    ['/etc/slp.conf'] = 'slpconf',
    ['/etc/slp.reg'] = 'slpreg',
    ['/etc/slp.spi'] = 'slpspi',
    ['.slrnrc'] = 'slrnrc',
    ['sendmail.cf'] = 'sm',
    ['squid.conf'] = 'squid',
    ['ssh_config'] = 'sshconfig',
    ['sshd_config'] = 'sshdconfig',
    ['/etc/sudoers'] = 'sudoers',
    ['sudoers.tmp'] = 'sudoers',
    ['/etc/sysctl.conf'] = 'sysctl',
    tags = 'tags',
    ['pending.data'] = 'taskdata',
    ['completed.data'] = 'taskdata',
    ['undo.data'] = 'taskdata',
    ['.tclshrc'] = 'tcl',
    ['.wishrc'] = 'tcl',
    ['tclsh.rc'] = 'tcl',
    ['texmf.cnf'] = 'texmf',
    COPYING = 'text',
    README = 'text',
    LICENSE = 'text',
    AUTHORS = 'text',
    tfrc = 'tf',
    ['.tfrc'] = 'tf',
    ['tidy.conf'] = 'tidy',
    tidyrc = 'tidy',
    ['.tidyrc'] = 'tidy',
    ['.tmux.conf'] = 'tmux',
    ['/.cargo/config'] = 'toml',
    Pipfile = 'toml',
    ['Gopkg.lock'] = 'toml',
    ['/.cargo/credentials'] = 'toml',
    ['Cargo.lock'] = 'toml',
    ['trustees.conf'] = 'trustees',
    ['/etc/udev/udev.conf'] = 'udevconf',
    ['/etc/updatedb.conf'] = 'updatedb',
    ['fdrupstream.log'] = 'upstreamlog',
    vgrindefs = 'vgrindefs',
    ['.exrc'] = 'vim',
    ['_exrc'] = 'vim',
    ['_viminfo'] = 'viminfo',
    ['.viminfo'] = 'viminfo',
    ['.wgetrc'] = 'wget',
    ['.wget2rc'] = 'wget2',
    wgetrc = 'wget',
    wget2rc = 'wget2',
    ['.wvdialrc'] = 'wvdial',
    ['wvdial.conf'] = 'wvdial',
    ['.Xresources'] = 'xdefaults',
    ['.Xpdefaults'] = 'xdefaults',
    ['xdm-config'] = 'xdefaults',
    ['.Xdefaults'] = 'xdefaults',
    ['xorg.conf'] = function(path, bufnr)
        return 'xf86conf', function(b)
            vim.b[b].xf86conf_xfree86_version = 4
        end
    end,
    ['xorg.conf-4'] = function(path, bufnr)
        return 'xf86conf', function(b)
            vim.b[b].xf86conf_xfree86_version = 4
        end
    end,
    ['XF86Config'] = function(path, bufnr)
        return require('vim.filetype.detect').xfree86()
    end,
    ['/etc/xinetd.conf'] = 'xinetd',
    fglrxrc = 'xml',
    ['/etc/blkid.tab'] = 'xml',
    ['/etc/blkid.tab.old'] = 'xml',
    ['/etc/zprofile'] = 'zsh',
    ['.zlogin'] = 'zsh',
    ['.zlogout'] = 'zsh',
    ['.zshrc'] = 'zsh',
    ['.zprofile'] = 'zsh',
    ['.zcompdump'] = 'zsh',
    ['.zshenv'] = 'zsh',
    ['.zfbfmarks'] = 'zsh',
    -- END FILENAME
}

local pattern = {
    -- BEGIN PATTERN
    ['.*/etc/a2ps/.*%.cfg'] = 'a2ps',
    ['.*/etc/a2ps%.cfg'] = 'a2ps',
    ['.*/usr/share/alsa/alsa%.conf'] = 'alsaconf',
    ['.*/etc/asound%.conf'] = 'alsaconf',
    ['.*/etc/apache2/sites%-.*/.*%.com'] = 'apache',
    ['.*/etc/httpd/.*%.conf'] = 'apache',
    ['.*/etc/apache2/.*%.conf.*'] = starsetf('apache'),
    ['.*/etc/apache2/conf%..*/.*'] = starsetf('apache'),
    ['.*/etc/apache2/mods%-.*/.*'] = starsetf('apache'),
    ['.*/etc/apache2/sites%-.*/.*'] = starsetf('apache'),
    ['access%.conf.*'] = starsetf('apache'),
    ['apache%.conf.*'] = starsetf('apache'),
    ['apache2%.conf.*'] = starsetf('apache'),
    ['httpd%.conf.*'] = starsetf('apache'),
    ['srm%.conf.*'] = starsetf('apache'),
    ['.*/etc/httpd/conf%..*/.*'] = starsetf('apache'),
    ['.*/etc/httpd/conf%.d/.*%.conf.*'] = starsetf('apache'),
    ['.*/etc/httpd/mods%-.*/.*'] = starsetf('apache'),
    ['.*/etc/httpd/sites%-.*/.*'] = starsetf('apache'),
    ['.*/etc/proftpd/.*%.conf.*'] = starsetf('apachestyle'),
    ['.*/etc/proftpd/conf%..*/.*'] = starsetf('apachestyle'),
    ['proftpd%.conf.*'] = starsetf('apachestyle'),
    ['.*asterisk/.*%.conf.*'] = starsetf('asterisk'),
    ['.*asterisk.*/.*voicemail%.conf.*'] = starsetf('asteriskvm'),
    ['.*/%.aptitude/config'] = 'aptconf',
    ['.*%.[aA]'] = function(path, bufnr)
        return require('vim.filetype.detect').asm(bufnr)
    end,
    ['.*%.[sS]'] = function(path, bufnr)
        return require('vim.filetype.detect').asm(bufnr)
    end,
    ['[mM]akefile%.am'] = 'automake',
    ['.*/bind/db%..*'] = starsetf('bindzone'),
    ['.*/named/db%..*'] = starsetf('bindzone'),
    ['.*/build/conf/.*%.conf'] = 'bitbake',
    ['.*/meta/conf/.*%.conf'] = 'bitbake',
    ['.*/meta%-.*/conf/.*%.conf'] = 'bitbake',
    ['bzr_log%..*'] = 'bzr',
    ['.*enlightenment/.*%.cfg'] = 'c',
    ['${HOME}/cabal%.config'] = 'cabalconfig',
    ['cabal%.project%..*'] = starsetf('cabalproject'),
    ['.*/%.calendar/.*'] = starsetf('calendar'),
    ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'),
    ['.*/share/calendar/calendar%..*'] = starsetf('calendar'),
    ['sgml%.catalog.*'] = starsetf('catalog'),
    ['.*/etc/defaults/cdrdao'] = 'cdrdaoconf',
    ['.*/etc/cdrdao%.conf'] = 'cdrdaoconf',
    ['.*/etc/default/cdrdao'] = 'cdrdaoconf',
    ['.*hgrc'] = 'cfg',
    ['.*%.[Cc][Ff][Gg]'] = {
        function(path, bufnr)
            return require('vim.filetype.detect').cfg(bufnr)
        end,
        -- Decrease priority to avoid conflicts with more specific patterns
        -- such as '.*/etc/a2ps/.*%.cfg', '.*enlightenment/.*%.cfg', etc.
        { priority = -1 },
    },
    ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr)
        return require('vim.filetype.detect').changelog(bufnr)
    end),
    ['.*%.%.ch'] = 'chill',
    ['.*%.cmake%.in'] = 'cmake',
    -- */cmus/rc and */.cmus/rc
    ['.*/%.?cmus/rc'] = 'cmusrc',
    -- */cmus/*.theme and */.cmus/*.theme
    ['.*/%.?cmus/.*%.theme'] = 'cmusrc',
    ['.*/%.cmus/autosave'] = 'cmusrc',
    ['.*/%.cmus/command%-history'] = 'cmusrc',
    ['.*/etc/hostname%..*'] = starsetf('config'),
    ['crontab%..*'] = starsetf('crontab'),
    ['.*/etc/cron%.d/.*'] = starsetf('crontab'),
    ['%.cshrc.*'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['%.login.*'] = function(path, bufnr)
        return require('vim.filetype.detect').csh(path, bufnr)
    end,
    ['cvs%d+'] = 'cvs',
    ['.*%.[Dd][Aa][Tt]'] = function(path, bufnr)
        return require('vim.filetype.detect').dat(path, bufnr)
    end,
    ['.*/debian/patches/.*'] = function(path, bufnr)
        return require('vim.filetype.detect').dep3patch(path, bufnr)
    end,
    ['.*/etc/dnsmasq%.d/.*'] = starsetf('dnsmasq'),
    ['Containerfile%..*'] = starsetf('dockerfile'),
    ['Dockerfile%..*'] = starsetf('dockerfile'),
    ['.*/etc/yum%.repos%.d/.*'] = starsetf('dosini'),
    ['drac%..*'] = starsetf('dracula'),
    ['.*/debian/changelog'] = 'debchangelog',
    ['.*/debian/control'] = 'debcontrol',
    ['.*/debian/copyright'] = 'debcopyright',
    ['.*/etc/apt/sources%.list%.d/.*%.list'] = 'debsources',
    ['.*/etc/apt/sources%.list'] = 'debsources',
    ['.*%.directory'] = 'desktop',
    ['.*%.desktop'] = 'desktop',
    ['dictd.*%.conf'] = 'dictdconf',
    ['.*/etc/DIR_COLORS'] = 'dircolors',
    ['.*/etc/dnsmasq%.conf'] = 'dnsmasq',
    ['php%.ini%-.*'] = 'dosini',
    ['.*/etc/pacman%.conf'] = 'confini',
    ['.*/etc/yum%.conf'] = 'dosini',
    ['.*lvs'] = 'dracula',
    ['.*lpe'] = 'dracula',
    ['.*/dtrace/.*%.d'] = 'dtrace',
    ['.*esmtprc'] = 'esmtprc',
    ['.*Eterm/.*%.cfg'] = 'eterm',
    ['[a-zA-Z0-9].*Dict'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['[a-zA-Z0-9].*Dict%..*'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['[a-zA-Z].*Properties'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['[a-zA-Z].*Properties%..*'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['.*Transport%..*'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['.*/constant/g'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['.*/0/.*'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['.*/0%.orig/.*'] = function(path, bufnr)
        return require('vim.filetype.detect').foam(bufnr)
    end,
    ['.*/%.fvwm/.*'] = starsetf('fvwm'),
    ['.*fvwmrc.*'] = starsetf(function(path, bufnr)
        return 'fvwm', function(b)
            vim.b[b].fvwm_version = 1
        end
    end),
    ['.*fvwm95.*%.hook'] = starsetf(function(path, bufnr)
        return 'fvwm', function(b)
            vim.b[b].fvwm_version = 1
        end
    end),
    ['.*fvwm2rc.*'] = starsetf(function(path, bufnr)
        return require('vim.filetype.detect').fvwm(path)
    end),
    ['.*/tmp/lltmp.*'] = starsetf('gedcom'),
    ['.*/etc/gitconfig%.d/.*'] = starsetf('gitconfig'),
    ['.*/gitolite%-admin/conf/.*'] = starsetf('gitolite'),
    ['tmac%..*'] = starsetf('nroff'),
    ['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'),
    ['.*%.git/.*'] = {
        function(path, bufnr)
            return require('vim.filetype.detect').git(bufnr)
        end,
        -- Decrease priority to run after simple pattern checks
        { priority = -1 },
    },
    ['.*%.git/modules/.*/config'] = 'gitconfig',
    ['.*%.git/modules/config'] = 'gitconfig',
    ['.*%.git/config'] = 'gitconfig',
    ['.*/etc/gitconfig'] = 'gitconfig',
    ['.*/%.config/git/config'] = 'gitconfig',
    ['.*%.git/config%.worktree'] = 'gitconfig',
    ['.*%.git/worktrees/.*/config%.worktree'] = 'gitconfig',
    ['${XDG_CONFIG_HOME}/git/config'] = 'gitconfig',
    ['.*%.git/info/attributes'] = 'gitattributes',
    ['.*/etc/gitattributes'] = 'gitattributes',
    ['.*/%.config/git/attributes'] = 'gitattributes',
    ['${XDG_CONFIG_HOME}/git/attributes'] = 'gitattributes',
    ['.*%.git/info/exclude'] = 'gitignore',
    ['.*/%.config/git/ignore'] = 'gitignore',
    ['${XDG_CONFIG_HOME}/git/ignore'] = 'gitignore',
    ['%.gitsendemail%.msg%.......'] = 'gitsendemail',
    ['gkrellmrc_.'] = 'gkrellmrc',
    ['.*/usr/.*/gnupg/options%.skel'] = 'gpg',
    ['.*/%.gnupg/options'] = 'gpg',
    ['.*/%.gnupg/gpg%.conf'] = 'gpg',
    ['${GNUPGHOME}/options'] = 'gpg',
    ['${GNUPGHOME}/gpg%.conf'] = 'gpg',
    ['.*/etc/group'] = 'group',
    ['.*/etc/gshadow'] = 'group',
    ['.*/etc/group%.edit'] = 'group',
    ['.*/var/backups/gshadow%.bak'] = 'group',
    ['.*/etc/group%-'] = 'group',
    ['.*/etc/gshadow%-'] = 'group',
    ['.*/var/backups/group%.bak'] = 'group',
    ['.*/etc/gshadow%.edit'] = 'group',
    ['.*/boot/grub/grub%.conf'] = 'grub',
    ['.*/boot/grub/menu%.lst'] = 'grub',
    ['.*/etc/grub%.conf'] = 'grub',
    -- gtkrc* and .gtkrc*
    ['%.?gtkrc.*'] = starsetf('gtkrc'),
    ['${VIMRUNTIME}/doc/.*%.txt'] = 'help',
    ['hg%-editor%-.*%.txt'] = 'hgcommit',
    ['.*/etc/host%.conf'] = 'hostconf',
    ['.*/etc/hosts%.deny'] = 'hostsaccess',
    ['.*/etc/hosts%.allow'] = 'hostsaccess',
    ['.*%.html%.m4'] = 'htmlm4',
    ['.*/%.i3/config'] = 'i3config',
    ['.*/i3/config'] = 'i3config',
    ['.*/%.icewm/menu'] = 'icemenu',
    ['.*/etc/initng/.*/.*%.i'] = 'initng',
    ['JAM.*%..*'] = starsetf('jam'),
    ['Prl.*%..*'] = starsetf('jam'),
    ['.*%.properties_..'] = 'jproperties',
    ['.*%.properties_.._..'] = 'jproperties',
    ['.*%.properties_.._.._.*'] = starsetf('jproperties'),
    ['Kconfig%..*'] = starsetf('kconfig'),
    ['.*%.[Ss][Uu][Bb]'] = 'krl',
    ['lilo%.conf.*'] = starsetf('lilo'),
    ['.*/etc/logcheck/.*%.d.*/.*'] = starsetf('logcheck'),
    ['.*lftp/rc'] = 'lftp',
    ['.*/%.libao'] = 'libao',
    ['.*/etc/libao%.conf'] = 'libao',
    ['.*/etc/.*limits%.conf'] = 'limits',
    ['.*/etc/limits'] = 'limits',
    ['.*/etc/.*limits%.d/.*%.conf'] = 'limits',
    ['.*/LiteStep/.*/.*%.rc'] = 'litestep',
    ['.*/etc/login%.access'] = 'loginaccess',
    ['.*/etc/login%.defs'] = 'logindefs',
    ['%.letter%.%d+'] = 'mail',
    ['%.article%.%d+'] = 'mail',
    ['/tmp/SLRN[0-9A-Z.]+'] = 'mail',
    ['ae%d+%.txt'] = 'mail',
    ['pico%.%d+'] = 'mail',
    ['mutt%-.*%-%w+'] = 'mail',
    ['muttng%-.*%-%w+'] = 'mail',
    ['neomutt%-.*%-%w+'] = 'mail',
    ['mutt' .. string.rep('[%w_-]', 6)] = 'mail',
    ['neomutt' .. string.rep('[%w_-]', 6)] = 'mail',
    ['snd%.%d+'] = 'mail',
    ['reportbug%-.*'] = starsetf('mail'),
    ['.*/etc/mail/aliases'] = 'mailaliases',
    ['.*/etc/aliases'] = 'mailaliases',
    ['.*[mM]akefile'] = 'make',
    ['[mM]akefile.*'] = starsetf('make'),
    ['.*/etc/man%.conf'] = 'manconf',
    ['.*/log/auth'] = 'messages',
    ['.*/log/cron'] = 'messages',
    ['.*/log/daemon'] = 'messages',
    ['.*/log/debug'] = 'messages',
    ['.*/log/kern'] = 'messages',
    ['.*/log/lpr'] = 'messages',
    ['.*/log/mail'] = 'messages',
    ['.*/log/messages'] = 'messages',
    ['.*/log/news/news'] = 'messages',
    ['.*/log/syslog'] = 'messages',
    ['.*/log/user'] = 'messages',
    ['.*/log/auth%.log'] = 'messages',
    ['.*/log/cron%.log'] = 'messages',
    ['.*/log/daemon%.log'] = 'messages',
    ['.*/log/debug%.log'] = 'messages',
    ['.*/log/kern%.log'] = 'messages',
    ['.*/log/lpr%.log'] = 'messages',
    ['.*/log/mail%.log'] = 'messages',
    ['.*/log/messages%.log'] = 'messages',
    ['.*/log/news/news%.log'] = 'messages',
    ['.*/log/syslog%.log'] = 'messages',
    ['.*/log/user%.log'] = 'messages',
    ['.*/log/auth%.err'] = 'messages',
    ['.*/log/cron%.err'] = 'messages',
    ['.*/log/daemon%.err'] = 'messages',
    ['.*/log/debug%.err'] = 'messages',
    ['.*/log/kern%.err'] = 'messages',
    ['.*/log/lpr%.err'] = 'messages',
    ['.*/log/mail%.err'] = 'messages',
    ['.*/log/messages%.err'] = 'messages',
    ['.*/log/news/news%.err'] = 'messages',
    ['.*/log/syslog%.err'] = 'messages',
    ['.*/log/user%.err'] = 'messages',
    ['.*/log/auth%.info'] = 'messages',
    ['.*/log/cron%.info'] = 'messages',
    ['.*/log/daemon%.info'] = 'messages',
    ['.*/log/debug%.info'] = 'messages',
    ['.*/log/kern%.info'] = 'messages',
    ['.*/log/lpr%.info'] = 'messages',
    ['.*/log/mail%.info'] = 'messages',
    ['.*/log/messages%.info'] = 'messages',
    ['.*/log/news/news%.info'] = 'messages',
    ['.*/log/syslog%.info'] = 'messages',
    ['.*/log/user%.info'] = 'messages',
    ['.*/log/auth%.warn'] = 'messages',
    ['.*/log/cron%.warn'] = 'messages',
    ['.*/log/daemon%.warn'] = 'messages',
    ['.*/log/debug%.warn'] = 'messages',
    ['.*/log/kern%.warn'] = 'messages',
    ['.*/log/lpr%.warn'] = 'messages',
    ['.*/log/mail%.warn'] = 'messages',
    ['.*/log/messages%.warn'] = 'messages',
    ['.*/log/news/news%.warn'] = 'messages',
    ['.*/log/syslog%.warn'] = 'messages',
    ['.*/log/user%.warn'] = 'messages',
    ['.*/log/auth%.crit'] = 'messages',
    ['.*/log/cron%.crit'] = 'messages',
    ['.*/log/daemon%.crit'] = 'messages',
    ['.*/log/debug%.crit'] = 'messages',
    ['.*/log/kern%.crit'] = 'messages',
    ['.*/log/lpr%.crit'] = 'messages',
    ['.*/log/mail%.crit'] = 'messages',
    ['.*/log/messages%.crit'] = 'messages',
    ['.*/log/news/news%.crit'] = 'messages',
    ['.*/log/syslog%.crit'] = 'messages',
    ['.*/log/user%.crit'] = 'messages',
    ['.*/log/auth%.notice'] = 'messages',
    ['.*/log/cron%.notice'] = 'messages',
    ['.*/log/daemon%.notice'] = 'messages',
    ['.*/log/debug%.notice'] = 'messages',
    ['.*/log/kern%.notice'] = 'messages',
    ['.*/log/lpr%.notice'] = 'messages',
    ['.*/log/mail%.notice'] = 'messages',
    ['.*/log/messages%.notice'] = 'messages',
    ['.*/log/news/news%.notice'] = 'messages',
    ['.*/log/syslog%.notice'] = 'messages',
    ['.*/log/user%.notice'] = 'messages',
    ['.*%.[Mm][Oo][Dd]'] = function(path, bufnr)
        return require('vim.filetype.detect').mod(path, bufnr)
    end,
    ['.*/etc/modules%.conf'] = 'modconf',
    ['.*/etc/conf%.modules'] = 'modconf',
    ['.*/etc/modules'] = 'modconf',
    ['.*/etc/modprobe%..*'] = starsetf('modconf'),
    ['.*/etc/modutils/.*'] = starsetf(function(path, bufnr)
        if vim.fn.executable(vim.fn.expand(path)) ~= 1 then
            return 'modconf'
        end
    end),
    ['.*%.[mi][3g]'] = 'modula3',
    ['Muttrc'] = 'muttrc',
    ['Muttngrc'] = 'muttrc',
    ['.*/etc/Muttrc%.d/.*'] = starsetf('muttrc'),
    ['.*/%.mplayer/config'] = 'mplayerconf',
    ['Muttrc.*'] = starsetf('muttrc'),
    ['Muttngrc.*'] = starsetf('muttrc'),
    -- muttrc* and .muttrc*
    ['%.?muttrc.*'] = starsetf('muttrc'),
    -- muttngrc* and .muttngrc*
    ['%.?muttngrc.*'] = starsetf('muttrc'),
    ['.*/%.mutt/muttrc.*'] = starsetf('muttrc'),
    ['.*/%.muttng/muttrc.*'] = starsetf('muttrc'),
    ['.*/%.muttng/muttngrc.*'] = starsetf('muttrc'),
    ['rndc.*%.conf'] = 'named',
    ['rndc.*%.key'] = 'named',
    ['named.*%.conf'] = 'named',
    ['.*/etc/nanorc'] = 'nanorc',
    ['.*%.NS[ACGLMNPS]'] = 'natural',
    ['Neomuttrc.*'] = starsetf('neomuttrc'),
    -- neomuttrc* and .neomuttrc*
    ['%.?neomuttrc.*'] = starsetf('neomuttrc'),
    ['.*/%.neomutt/neomuttrc.*'] = starsetf('neomuttrc'),
    ['nginx.*%.conf'] = 'nginx',
    ['.*/etc/nginx/.*'] = 'nginx',
    ['.*nginx%.conf'] = 'nginx',
    ['.*/nginx/.*%.conf'] = 'nginx',
    ['.*/usr/local/nginx/conf/.*'] = 'nginx',
    ['.*%.[1-9]'] = function(path, bufnr)
        return require('vim.filetype.detect').nroff(bufnr)
    end,
    ['.*%.ml%.cppo'] = 'ocaml',
    ['.*%.mli%.cppo'] = 'ocaml',
    ['.*%.opam%.template'] = 'opam',
    ['.*%.[Oo][Pp][Ll]'] = 'opl',
    ['.*/etc/pam%.conf'] = 'pamconf',
    ['.*/etc/pam%.d/.*'] = starsetf('pamconf'),
    ['.*/etc/passwd%-'] = 'passwd',
    ['.*/etc/shadow'] = 'passwd',
    ['.*/etc/shadow%.edit'] = 'passwd',
    ['.*/var/backups/shadow%.bak'] = 'passwd',
    ['.*/var/backups/passwd%.bak'] = 'passwd',
    ['.*/etc/passwd'] = 'passwd',
    ['.*/etc/passwd%.edit'] = 'passwd',
    ['.*/etc/shadow%-'] = 'passwd',
    ['%.?gitolite%.rc'] = 'perl',
    ['example%.gitolite%.rc'] = 'perl',
    ['.*%.php%d'] = 'php',
    ['.*/%.pinforc'] = 'pinfo',
    ['.*/etc/pinforc'] = 'pinfo',
    ['.*%.[Pp][Rr][Gg]'] = function(path, bufnr)
        return require('vim.filetype.detect').prg(bufnr)
    end,
    ['.*/etc/protocols'] = 'protocols',
    ['.*printcap.*'] = starsetf(function(path, bufnr)
        return require('vim.filetype.detect').printcap('print')
    end),
    ['.*baseq[2-3]/.*%.cfg'] = 'quake',
    ['.*quake[1-3]/.*%.cfg'] = 'quake',
    ['.*id1/.*%.cfg'] = 'quake',
    ['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries (Neovim only)
    ['.*,v'] = 'rcs',
    ['%.reminders.*'] = starsetf('remind'),
    ['[rR]akefile.*'] = starsetf('ruby'),
    ['[rR]antfile'] = 'ruby',
    ['[rR]akefile'] = 'ruby',
    ['.*/etc/sensors%.d/[^.].*'] = starsetf('sensors'),
    ['.*/etc/sensors%.conf'] = 'sensors',
    ['.*/etc/sensors3%.conf'] = 'sensors',
    ['.*/etc/services'] = 'services',
    ['.*/etc/serial%.conf'] = 'setserial',
    ['.*/etc/udev/cdsymlinks%.conf'] = 'sh',
    ['%.bash[_%-]aliases'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['%.bash[_%-]logout'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['%.bash[_%-]profile'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['%.kshrc.*'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
    end,
    ['%.profile.*'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    ['.*/etc/profile'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
    end,
    ['bash%-fc[%-%.]'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
    end,
    ['%.tcshrc.*'] = function(path, bufnr)
        return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'tcsh')
    end,
    ['.*/etc/sudoers%.d/.*'] = starsetf('sudoers'),
    ['.*%._sst%.meta'] = 'sisu',
    ['.*%.%-sst%.meta'] = 'sisu',
    ['.*%.sst%.meta'] = 'sisu',
    ['.*/etc/slp%.conf'] = 'slpconf',
    ['.*/etc/slp%.reg'] = 'slpreg',
    ['.*/etc/slp%.spi'] = 'slpspi',
    ['.*/etc/ssh/ssh_config%.d/.*%.conf'] = 'sshconfig',
    ['.*/%.ssh/config'] = 'sshconfig',
    ['.*/%.ssh/.*%.conf'] = 'sshconfig',
    ['.*/etc/ssh/sshd_config%.d/.*%.conf'] = 'sshdconfig',
    ['.*%.[Ss][Rr][Cc]'] = function(path, bufnr)
        return require('vim.filetype.detect').src(bufnr)
    end,
    ['.*/etc/sudoers'] = 'sudoers',
    ['svn%-commit.*%.tmp'] = 'svn',
    ['.*/sway/config'] = 'swayconfig',
    ['.*/%.sway/config'] = 'swayconfig',
    ['.*%.swift%.gyb'] = 'swiftgyb',
    ['.*%.[Ss][Yy][Ss]'] = function(path, bufnr)
        return require('vim.filetype.detect').sys(bufnr)
    end,
    ['.*/etc/sysctl%.conf'] = 'sysctl',
    ['.*/etc/sysctl%.d/.*%.conf'] = 'sysctl',
    ['.*/systemd/.*%.automount'] = 'systemd',
    ['.*/systemd/.*%.dnssd'] = 'systemd',
    ['.*/systemd/.*%.link'] = 'systemd',
    ['.*/systemd/.*%.mount'] = 'systemd',
    ['.*/systemd/.*%.netdev'] = 'systemd',
    ['.*/systemd/.*%.network'] = 'systemd',
    ['.*/systemd/.*%.nspawn'] = 'systemd',
    ['.*/systemd/.*%.path'] = 'systemd',
    ['.*/systemd/.*%.service'] = 'systemd',
    ['.*/systemd/.*%.slice'] = 'systemd',
    ['.*/systemd/.*%.socket'] = 'systemd',
    ['.*/systemd/.*%.swap'] = 'systemd',
    ['.*/systemd/.*%.target'] = 'systemd',
    ['.*/systemd/.*%.timer'] = 'systemd',
    ['.*/etc/systemd/.*%.conf%.d/.*%.conf'] = 'systemd',
    ['.*/%.config/systemd/user/.*%.d/.*%.conf'] = 'systemd',
    ['.*/etc/systemd/system/.*%.d/.*%.conf'] = 'systemd',
    ['.*/etc/systemd/system/.*%.d/%.#.*'] = 'systemd',
    ['.*/etc/systemd/system/%.#.*'] = 'systemd',
    ['.*/%.config/systemd/user/.*%.d/%.#.*'] = 'systemd',
    ['.*/%.config/systemd/user/%.#.*'] = 'systemd',
    ['.*termcap.*'] = starsetf(function(path, bufnr)
        return require('vim.filetype.detect').printcap('term')
    end),
    ['.*%.t%.html'] = 'tilde',
    ['%.?tmux.*%.conf'] = 'tmux',
    ['%.?tmux.*%.conf.*'] = { 'tmux', { priority = -1 } },
    ['.*/%.cargo/config'] = 'toml',
    ['.*/%.cargo/credentials'] = 'toml',
    ['.*/etc/udev/udev%.conf'] = 'udevconf',
    ['.*/etc/udev/permissions%.d/.*%.permissions'] = 'udevperm',
    ['.*/etc/updatedb%.conf'] = 'updatedb',
    ['.*/%.init/.*%.override'] = 'upstart',
    ['.*/usr/share/upstart/.*%.conf'] = 'upstart',
    ['.*/%.config/upstart/.*%.override'] = 'upstart',
    ['.*/etc/init/.*%.conf'] = 'upstart',
    ['.*/etc/init/.*%.override'] = 'upstart',
    ['.*/%.config/upstart/.*%.conf'] = 'upstart',
    ['.*/%.init/.*%.conf'] = 'upstart',
    ['.*/usr/share/upstart/.*%.override'] = 'upstart',
    ['.*%.[Ll][Oo][Gg]'] = function(path, bufnr)
        return require('vim.filetype.detect').log(path)
    end,
    ['.*%.vhdl_[0-9].*'] = starsetf('vhdl'),
    ['.*%.ws[fc]'] = 'wsh',
    ['.*/Xresources/.*'] = starsetf('xdefaults'),
    ['.*/app%-defaults/.*'] = starsetf('xdefaults'),
    ['.*/etc/xinetd%.conf'] = 'xinetd',
    ['.*/etc/blkid%.tab'] = 'xml',
    ['.*/etc/blkid%.tab%.old'] = 'xml',
    ['.*%.vbproj%.user'] = 'xml',
    ['.*%.fsproj%.user'] = 'xml',
    ['.*%.csproj%.user'] = 'xml',
    ['.*/etc/xdg/menus/.*%.menu'] = 'xml',
    ['.*Xmodmap'] = 'xmodmap',
    ['.*/etc/zprofile'] = 'zsh',
    ['.*vimrc.*'] = starsetf('vim'),
    ['Xresources.*'] = starsetf('xdefaults'),
    ['.*/etc/xinetd%.d/.*'] = starsetf('xinetd'),
    ['.*xmodmap.*'] = starsetf('xmodmap'),
    ['.*/xorg%.conf%.d/.*%.conf'] = function(path, bufnr)
        return 'xf86config', function(b)
            vim.b[b].xf86conf_xfree86_version = 4
        end
    end,
    -- Increase priority to run before the pattern below
    ['XF86Config%-4.*'] = starsetf(function(path, bufnr)
        return 'xf86conf', function(b)
            vim.b[b].xf86conf_xfree86_version = 4
        end
    end, { priority = -math.huge + 1 }),
    ['XF86Config.*'] = starsetf(function(path, bufnr)
        return require('vim.filetype.detect').xfree86()
    end),
    ['%.zcompdump.*'] = starsetf('zsh'),
    -- .zlog* and zlog*
    ['%.?zlog.*'] = starsetf('zsh'),
    -- .zsh* and zsh*
    ['%.?zsh.*'] = starsetf('zsh'),
    -- Ignored extension
    ['.*~'] = function(path, bufnr)
        local short = path:gsub('~$', '', 1)
        if path ~= short and short ~= '' then
            return M.match({ buf = bufnr, filename = vim.fn.fnameescape(short) })
        end
    end,
    -- END PATTERN
}
-- luacheck: pop
-- luacheck: pop

---@private
local function sort_by_priority(t)
    local sorted = {}
    for k, v in pairs(t) do
        local ft = type(v) == 'table' and v[1] or v
        assert(
            type(ft) == 'string' or type(ft) == 'function',
            'Expected string or function for filetype'
        )

        local opts = (type(v) == 'table' and type(v[2]) == 'table') and v[2] or {}
        if not opts.priority then
            opts.priority = 0
        end
        table.insert(sorted, { [k] = { ft, opts } })
    end
    table.sort(sorted, function(a, b)
        return a[next(a)][2].priority > b[next(b)][2].priority
    end)
    return sorted
end

local pattern_sorted = sort_by_priority(pattern)

---@private
local function normalize_path(path, as_pattern)
    local normal = path:gsub('\\', '/')
    if normal:find('^~') then
        if as_pattern then
            -- Escape Lua's metacharacters when $HOME is used in a pattern.
            -- The rest of path should already be properly escaped.
            normal = vim.pesc(vim.env.HOME) .. normal:sub(2)
        else
            normal = vim.env.HOME .. normal:sub(2)
        end
    end
    return normal
end

--- Add new filetype mappings.
---
--- Filetype mappings can be added either by extension or by filename (either
--- the "tail" or the full file path). The full file path is checked first,
--- followed by the file name. If a match is not found using the filename, then
--- the filename is matched against the list of |lua-patterns| (sorted by priority)
--- until a match is found. Lastly, if pattern matching does not find a
--- filetype, then the file extension is used.
---
--- The filetype can be either a string (in which case it is used as the
--- filetype directly) or a function. If a function, it takes the full path and
--- buffer number of the file as arguments (along with captures from the matched
--- pattern, if any) and should return a string that will be used as the
--- buffer's filetype. Optionally, the function can return a second function
--- value which, when called, modifies the state of the buffer. This can be used
--- to, for example, set filetype-specific buffer variables.
---
--- Filename patterns can specify an optional priority to resolve cases when a
--- file path matches multiple patterns. Higher priorities are matched first.
--- When omitted, the priority defaults to 0.
--- A pattern can contain environment variables of the form "${SOME_VAR}" that will
--- be automatically expanded. If the environment variable is not set, the pattern
--- won't be matched.
---
--- See $VIMRUNTIME/lua/vim/filetype.lua for more examples.
---
--- Note that Lua filetype detection is disabled when |g:do_legacy_filetype| is set.
---
--- Example:
--- <pre>
---  vim.filetype.add({
---    extension = {
---      foo = 'fooscript',
---      bar = function(path, bufnr)
---        if some_condition() then
---          return 'barscript', function(bufnr)
---            -- Set a buffer variable
---            vim.b[bufnr].barscript_version = 2
---          end
---        end
---        return 'bar'
---      end,
---    },
---    filename = {
---      ['.foorc'] = 'toml',
---      ['/etc/foo/config'] = 'toml',
---    },
---    pattern = {
---      ['.*/etc/foo/.*'] = 'fooscript',
---      -- Using an optional priority
---      ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } },
---      -- A pattern containing an environment variable
---      ['${XDG_CONFIG_HOME}/foo/git'] = 'git',
---      ['README.(%a+)$'] = function(path, bufnr, ext)
---        if ext == 'md' then
---          return 'markdown'
---        elseif ext == 'rst' then
---          return 'rst'
---        end
---      end,
---    },
---  })
--- </pre>
---
--- To add a fallback match on contents (see |new-filetype-scripts|), use
--- <pre>
--- vim.filetype.add {
---   pattern = {
---     ['.*'] = {
---       priority = -math.huge,
---       function(path, bufnr)
---         local content = vim.filetype.getlines(bufnr, 1)
---         if vim.filetype.matchregex(content, [[^#!.*\\<mine\\>]]) then
---           return 'mine'
---         elseif vim.filetype.matchregex(content, [[\\<drawing\\>]]) then
---           return 'drawing'
---         end
---       end,
---     },
---   },
--- }
--- </pre>
---
---@param filetypes table A table containing new filetype maps (see example).
function M.add(filetypes)
    for k, v in pairs(filetypes.extension or {}) do
        extension[k] = v
    end

    for k, v in pairs(filetypes.filename or {}) do
        filename[normalize_path(k)] = v
    end

    for k, v in pairs(filetypes.pattern or {}) do
        pattern[normalize_path(k, true)] = v
    end

    if filetypes.pattern then
        pattern_sorted = sort_by_priority(pattern)
    end
end

---@private
local function dispatch(ft, path, bufnr, ...)
    local on_detect
    if type(ft) == 'function' then
        if bufnr then
            ft, on_detect = ft(path, bufnr, ...)
        else
            -- If bufnr is nil (meaning we are matching only against the filename), set it to an invalid
            -- value (-1) and catch any errors from the filetype detection function. If the function tries
            -- to use the buffer then it will fail, but this enables functions which do not need a buffer
            -- to still work.
            local ok
            ok, ft, on_detect = pcall(ft, path, -1, ...)
            if not ok then
                return
            end
        end
    end

    if type(ft) == 'string' then
        return ft, on_detect
    end
end

-- Lookup table/cache for patterns that contain an environment variable pattern, e.g. ${SOME_VAR}.
local expand_env_lookup = {}

---@private
local function match_pattern(name, path, tail, pat)
    if expand_env_lookup[pat] == nil then
        expand_env_lookup[pat] = pat:find('%${') ~= nil
    end
    if expand_env_lookup[pat] then
        local return_early
        pat = pat:gsub('%${(%S-)}', function(env)
            -- If an environment variable is present in the pattern but not set, there is no match
            if not vim.env[env] then
                return_early = true
                return nil
            end
            return vim.env[env]
        end)
        if return_early then
            return false
        end
    end
    -- If the pattern contains a / match against the full path, otherwise just the tail
    local fullpat = '^' .. pat .. '$'
    local matches
    if pat:find('/') then
        -- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against
        -- both the short file name (as typed) and the full file name (after expanding to full path
        -- and resolving symlinks)
        matches = name:match(fullpat) or path:match(fullpat)
    else
        matches = tail:match(fullpat)
    end
    return matches
end

--- Perform filetype detection.
---
--- The filetype can be detected using one of three methods:
---  1. Using an existing buffer
---  2. Using only a file name
---  3. Using only file contents
---
--- Of these, option 1 provides the most accurate result as it uses both the buffer's filename and
--- (optionally) the buffer contents. Options 2 and 3 can be used without an existing buffer, but
--- may not always provide a match in cases where the filename (or contents) cannot unambiguously
--- determine the filetype.
---
--- Each of the three options is specified using a key to the single argument of this function.
--- Example:
---
--- <pre>
---   -- Using a buffer number
---   vim.filetype.match({ buf = 42 })
---
---   -- Override the filename of the given buffer
---   vim.filetype.match({ buf = 42, filename = 'foo.c' })
---
---   -- Using a filename without a buffer
---   vim.filetype.match({ filename = 'main.lua' })
---
---   -- Using file contents
---   vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
--- </pre>
---
---@param args table Table specifying which matching strategy to use. Accepted keys are:
---                   * buf (number): Buffer number to use for matching. Mutually exclusive with
---                                   {contents}
---                   * filename (string): Filename to use for matching. When {buf} is given,
---                                        defaults to the filename of the given buffer number. The
---                                        file need not actually exist in the filesystem. When used
---                                        without {buf} only the name of the file is used for
---                                        filetype matching. This may result in failure to detect
---                                        the filetype in cases where the filename alone is not
---                                        enough to disambiguate the filetype.
---                   * contents (table): An array of lines representing file contents to use for
---                                       matching. Can be used with {filename}. Mutually exclusive
---                                       with {buf}.
---@return string|nil If a match was found, the matched filetype.
---@return function|nil A function that modifies buffer state when called (for example, to set some
---                     filetype specific buffer variables). The function accepts a buffer number as
---                     its only argument.
function M.match(args)
    vim.validate({
        arg = { args, 't' },
    })

    if not (args.buf or args.filename or args.contents) then
        error('At least one of "buf", "filename", or "contents" must be given')
    end

    local bufnr = args.buf
    local name = args.filename
    local contents = args.contents

    if bufnr and not name then
        name = api.nvim_buf_get_name(bufnr)
    end

    if name then
        name = normalize_path(name)
    end

    local ft, on_detect

    -- First check for the simple case where the full path exists as a key
    local path = vim.fn.fnamemodify(name, ':p')
    ft, on_detect = dispatch(filename[path], path, bufnr)
    if ft then
        return ft, on_detect
    end

    -- Next check against just the file name
    local tail = vim.fn.fnamemodify(name, ':t')
    ft, on_detect = dispatch(filename[tail], path, bufnr)
    if ft then
        return ft, on_detect
    end

    -- Next, check the file path against available patterns with non-negative priority
    local j = 1
    for i, v in ipairs(pattern_sorted) do
        local k = next(v)
        local opts = v[k][2]
        if opts.priority < 0 then
            j = i
            break
        end

        local filetype = v[k][1]
        local matches = match_pattern(name, path, tail, k)
        if matches then
            ft, on_detect = dispatch(filetype, path, bufnr, matches)
            if ft then
                return ft, on_detect
            end
        end
    end

    -- Next, check file extension
    local ext = vim.fn.fnamemodify(name, ':e')
    ft, on_detect = dispatch(extension[ext], path, bufnr)
    if ft then
        return ft, on_detect
    end

    -- Next, check patterns with negative priority
    for i = j, #pattern_sorted do
        local v = pattern_sorted[i]
        local k = next(v)

        local filetype = v[k][1]
        local matches = match_pattern(name, path, tail, k)
        if matches then
            ft, on_detect = dispatch(filetype, path, bufnr, matches)
            if ft then
                return ft, on_detect
            end
        end
    end

    -- Finally, check file contents
    if contents or bufnr then
        if contents == nil then
            if api.nvim_buf_line_count(bufnr) > 101 then
                -- only need first 100 and last line for current checks
                contents = M.getlines(bufnr, 1, 100)
                contents[#contents + 1] = M.getlines(bufnr, -1)
            else
                contents = M.getlines(bufnr)
            end
        end
        -- If name is nil, catch any errors from the contents filetype detection function.
        -- If the function tries to use the filename that is nil then it will fail,
        -- but this enables checks which do not need a filename to still work.
        local ok
        ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name)
        if ok and ft then
            return ft
        end
    end
end

return M
